package com.harmontronics.hanma.paas.paas.domain.releasesetup.service;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.harmontronics.hanma.paas.paas.common.CrossDomainFunction;
import com.harmontronics.hanma.paas.paas.consts.DbConstants;
import com.harmontronics.hanma.paas.paas.consts.ScriptConstants;
import com.harmontronics.hanma.paas.paas.consts.SupportedDatabaseEnum;
import com.harmontronics.hanma.paas.paas.domain.releasesetup.entity.script.ConnectionDefinitionBean;
import com.harmontronics.hanma.paas.paas.domain.releasesetup.entity.valueobject.HmReleaseSetupFields;
import com.harmontronics.hanma.paas.paas.domain.releasesetup.entity.valueobject.ReleaseStatusEnum;
import com.harmontronics.hanma.paas.paas.domain.releasesetup.repository.persistence.HmReleaseRecordRepository;
import com.harmontronics.hanma.paas.paas.domain.releasesetup.repository.persistence.HmReleaseSetupRepository;
import com.harmontronics.hanma.paas.paas.domain.releasesetup.repository.po.HmReleaseRecordPO;
import com.harmontronics.hanma.paas.paas.domain.releasesetup.repository.po.HmReleaseSetupPO;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.io.IOUtils;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author ldb
 */
@Service
public class PackService {

    private static final Map<String, Long> CURRENT_PACK_SEQUENCE_MAP = new ConcurrentHashMap<>();
    @Autowired
    private DynamicRoutingDataSource dynamicRoutingDataSource;
    @Autowired
    private HmReleaseRecordRepository hmReleaseRecordRepository;
    @Autowired
    private HmReleaseSetupRepository hmReleaseSetupRepository;


    public void exportSql(String dsName, File file, Long sequence, List<String> modelSqls) throws Exception {
        HikariDataSource dataSource = (HikariDataSource) dynamicRoutingDataSource.getDataSource(dsName);
        ConnectionDefinitionBean conSrc = new ConnectionDefinitionBean(dataSource.getJdbcUrl(), dataSource.getUsername(), dataSource.getPassword(), "public");
        ConnectionDefinitionBean conDest = new ConnectionDefinitionBean("postgresql", null, null, null);

        ExportRunner runner = new ExportRunner(conSrc, conDest, sequence, modelSqls);
        runner.outputScript(file);

    }


    public Long getPackSequence(String appUnique) {
        Long sequence = CURRENT_PACK_SEQUENCE_MAP.get(appUnique);
        if (sequence != null) {
            return sequence;
        }
        File appDir = makeAppDir(appUnique);
        File dbMirDir = new File(appDir, "dbMir");
        if (!dbMirDir.exists()) {
            sequence = 1L;
        } else {
            sequence = dbMirDir.listFiles().length + 1L;
        }

        CURRENT_PACK_SEQUENCE_MAP.put(appUnique, sequence);
        return sequence;
    }

    @Async
    public void doPack(HmReleaseRecordPO po,File appDir, String appName, String appIcon, List<String> modelSqls) {
        try {
            File dbMirDir = new File(appDir, "dbMir");
            File jarDir = new File(appDir, "jars");
            File processDir = new File(appDir,"process");
            //生成脚本
            String dsName = "hm_" + po.getAppUniqueId();
            long count = dbMirDir.listFiles().length;
            File sqlFile = new File(dbMirDir, "V1_0_0_" + (count + 1) + "__pack.sql");
            exportSql(dsName, sqlFile, getPackSequence(po.getAppUniqueId()), modelSqls);
            //生产yml
            File ymlFile = new File(jarDir, "application.yml");
            ClassPathResource templateFile = new ClassPathResource("application-template.yml");
            byte[] dataBytes = IOUtils.toByteArray(templateFile.getURL());
            String dataStr = new String(dataBytes);
            HmReleaseSetupPO setupPO = hmReleaseSetupRepository.findByAppUniqueAndSetupType(po.getAppUniqueId(), po.getSetupType());
            Map<String, Object> setupMap = BeanUtil.beanToMap(setupPO);
            setupMap.put("jdbcUrl", getJdbcUrl(setupPO));
            //加上双引号 防止0开头的字符串被当成八进制
            setupMap.put("appName", "\"" + appName + "\"");
            setupMap.put("env", po.getSetupType().name());
            StringSubstitutor sub = new StringSubstitutor(setupMap);
            String replacedStr = sub.replace(dataStr);
            Files.write(ymlFile.toPath(), replacedStr.getBytes(StandardCharsets.UTF_8));
            //生产ZIP包
            String zipName = "V1_0_0_" + (count + 1) + "_runner.zip";
            String jarName = "V1_0_0_" + (count + 1) + "_runner.jar";
            makeZip(dbMirDir, processDir, ymlFile, new File(jarDir, zipName), new File(jarDir, jarName), appIcon, po.getAppUniqueId());
            po.setStatus(ReleaseStatusEnum.FINISHED);
            po.setZipUrl("/release/" + HmReleaseSetupFields.PACK_ROOT_DIR + "/" + po.getAppUniqueId() + "/jars/" + zipName);
            hmReleaseRecordRepository.updateById(po);
            CURRENT_PACK_SEQUENCE_MAP.remove(po.getAppUniqueId());
        } catch (Exception ex) {
            ex.printStackTrace();
            po.setStatus(ReleaseStatusEnum.FAIL);
            hmReleaseRecordRepository.updateById(po);
        }
    }

    private String getJdbcUrl(HmReleaseSetupPO po) {
        String template = "jdbc:%s://%s:%s/%s";

        if (po.getDatabaseType().equalsIgnoreCase(SupportedDatabaseEnum.POSTGRES.name())) {
            return String.format(template, "postgresql", po.getDatabaseUrl(), po.getDatabasePort(), po.getDatabaseName());
        }
        return null;
    }

    private void makeZip(File dbMirDir, File processDir, File ymlFile, File zipFile, File jarFile, String appIcon, String appUnique) throws IOException {
        ClassPathResource templateFile = new ClassPathResource("sjar/paas-start-1.jar");
        if (zipFile.exists()) {
            zipFile.delete();
        }
        if (jarFile.exists()) {
            jarFile.delete();
        }

        try (ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(zipFile))) {
            outputStream.setLevel(Deflater.NO_COMPRESSION);
            ZipEntry ymlEntry = new ZipEntry("application.yml");
            outputStream.putNextEntry(ymlEntry);
            outputStream.write(Files.readAllBytes(ymlFile.toPath()));
            outputStream.closeEntry();
            ZipEntry jarEntry = new ZipEntry("pass-start.jar");
            outputStream.putNextEntry(jarEntry);
            try (InputStream is = templateFile.getInputStream()) {
                outputStream.write(IOUtils.toByteArray(is));
            }
            outputStream.closeEntry();
            ZipEntry logoEntry = new ZipEntry("logo.txt");
            outputStream.putNextEntry(logoEntry);
            outputStream.write(appIcon.getBytes(StandardCharsets.UTF_8));
            outputStream.closeEntry();
            for (File sqlFile : dbMirDir.listFiles()) {
                String name = sqlFile.getName();
                ZipEntry entry = new ZipEntry("db/migration/" + name);
                byte[] data = Files.readAllBytes(sqlFile.toPath());
                outputStream.putNextEntry(entry);
                outputStream.write(data);
                outputStream.closeEntry();
            }
            for (File processFile : processDir.listFiles()) {
                String name = processFile.getName();
                ZipEntry entry = new ZipEntry("process/" + name);
                byte[] data = Files.readAllBytes(processFile.toPath());
                outputStream.putNextEntry(entry);
                outputStream.write(data);
                outputStream.closeEntry();
            }

            List<File> scriptFiles = getScriptFile(appUnique);
            if (CollectionUtils.isEmpty(scriptFiles)) {
                return;
            }
            String scriptDirInZip = ScriptConstants.SCRIPT_FILE_ROOT_DIR + File.separator + DbConstants.DS_MASTER_NAME;
            outputStream.putNextEntry(new ZipEntry(scriptDirInZip));
            outputStream.closeEntry();
            for (File scriptFile : scriptFiles) {
                outputStream.putNextEntry(new ZipEntry(scriptDirInZip + File.separator + scriptFile.getName()));
                outputStream.write(Files.readAllBytes(scriptFile.toPath()));
                outputStream.closeEntry();
            }

        }
    }

    public List<File> getScriptFile(String appUnique) {
        ApplicationHome home = new ApplicationHome(getClass());
        File dir = home.getDir();
        File rootDir = new File(dir, ScriptConstants.SCRIPT_FILE_ROOT_DIR);
        File dirFile = new File(rootDir, appUnique);
        File[] files = dirFile.listFiles();
        if (files == null || files.length == 0) {
            return new ArrayList<>();
        }
        return new ArrayList<>(Arrays.asList(files));
    }

    public File makeAppDir(String appUniqueId) {
        File rootDir = makeAndGetRootDir();
        File appDir = new File(rootDir, appUniqueId);
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        File dbMirDir = new File(appDir, "dbMir");
        if (!dbMirDir.exists()) {
            dbMirDir.mkdir();
        }

        File processDir = new File(appDir, "process");
        if (!processDir.exists()) {
            processDir.mkdir();
        }
        File jarDir = new File(appDir, "jars");
        if (!jarDir.exists()) {
            jarDir.mkdir();
        }
        return appDir;
    }

    public synchronized File makeAndGetRootDir() {
        ApplicationHome home = new ApplicationHome(getClass());
        File homeDir = home.getDir();
        File packRootDir = new File(homeDir, HmReleaseSetupFields.PACK_ROOT_DIR);
        if (!packRootDir.exists()) {
            packRootDir.mkdir();
        }
        return packRootDir;
    }
}
